; ====================================================================================
; kernel.asm
; 功能: 算不上内核的 kernel ，今后的内核就在这上面扩充
; 编译链接方法：
; 由于 ubuntu 20.04 是64位系统，编译链接 32 位文件要用以下命令
; nasm -f elf32 kernel.asm -o Output/kernel.o
; nasm -f elf32 string.asm -o Output/string.o
; nasm -f elf32 klib.asm -o Output/klib.o
; gcc -c -fno-builtin -m32 start.c -o Output/start.o
; gcc 编译时加上 -fno-builtin，否则会警告，因为 memcpy 默认是 built-in 函数
; ld -m elf_i386 Output/kernel.o Output/string.o Output/klib.o Output/start.o -o Output/kernel.bin
; 链接时加上 -Ttext 0x30400 指定链接生成的文件(elf)中代码段存放的内存位置
; ====================================================================================

%include "sconst.inc"

; 导入函数
extern	cstart
extern	xlinix_main
extern	exception_handler
extern	spurious_irq
extern	clock_handler
extern	disp_str
extern	delay

; 导入全局变量
extern	gdt_ptr
extern	idt_ptr
extern	p_proc_ready
extern	tss
extern	disp_pos
extern	k_reenter
extern	irq_table
extern	sys_call_table

bits 32

[SECTION .data]
clock_int_msg db "^", 0

[SECTION .bss]
StackSpace  resb	2 * 1024    ; resb 从当前位置开始保留指定数量的字节，但不初始化它们的值
StackTop:		                ; 栈顶 栈空间 10K

[section .text]
global _start                   ; 导出 _start
global restart
global sys_call

; 0 ~ 31 号中断
global	divide_error
global	single_step_exception
global	nmi
global	breakpoint_exception
global	overflow
global	bounds_check
global	inval_opcode
global	copr_not_available
global	double_fault
global	copr_seg_overrun
global	inval_tss
global	segment_not_present
global	stack_exception
global	general_protection
global	page_fault
global	copr_error
global	hwint00
global	hwint01
global	hwint02
global	hwint03
global	hwint04
global	hwint05
global	hwint06
global	hwint07
global	hwint08
global	hwint09
global	hwint10
global	hwint11
global	hwint12
global	hwint13
global	hwint14
global	hwint15

_start:
    ; 把 esp 从 LOADER 挪到 KERNEL
	mov	esp, StackTop	        ; 堆栈在 bss 段中

	mov	dword [disp_pos], 0

    sgdt    [gdt_ptr]           ; sgdt 读取当前GDTR寄存器 cstart() 中将会用到 gdt_ptr
    call    cstart              ; 在此函数中改变了gdt_ptr，让它指向新的GDT
    lgdt    [gdt_ptr]           ; 设置新的GDT 

    lidt    [idt_ptr]

    jmp	SELECTOR_KERNEL_CS:csinit
csinit:   
	xor eax, eax
	mov ax, SELECTOR_TSS
	ltr ax

    jmp	xlinix_main

; 中断和异常 -- 硬件中断
; ---------------------------------
%macro	hwint_master 1	
	call save

	; 不再允许发生时钟中断
	in	al, INT_M_CTLMASK
	or	al, (1 << %1)
	out	INT_M_CTLMASK, al

	; 将中断结束位置 1，告诉 8259A 当前中断结束
	mov al, EOI
	out INT_M_CTL, al

	sti			; CPU 在相应中断的过程中会自动关闭中断，打开中断
	push	%1	; 中断号
	call	[irq_table + 4 * %1]	; 更改进程表指向
	pop	ecx
	cli 		; 关闭中断

	; 又允许时钟中断
	in	al, INT_M_CTLMASK
	and	al, ~(1 << %1)
	out	INT_M_CTLMASK, al

	ret
%endmacro

ALIGN	16
hwint00:		; Interrupt routine for irq 0 (the clock).
	hwint_master	0

ALIGN	16
hwint01:		; Interrupt routine for irq 1 (keyboard)
	hwint_master	1

ALIGN	16
hwint02:		; Interrupt routine for irq 2 (cascade!)
	hwint_master	2

ALIGN	16
hwint03:		; Interrupt routine for irq 3 (second serial)
	hwint_master	3

ALIGN	16
hwint04:		; Interrupt routine for irq 4 (first serial)
	hwint_master	4

ALIGN	16
hwint05:		; Interrupt routine for irq 5 (XT winchester)
	hwint_master	5

ALIGN	16
hwint06:		; Interrupt routine for irq 6 (floppy)
	hwint_master	6

ALIGN	16
hwint07:		; Interrupt routine for irq 7 (printer)
	hwint_master	7

; ---------------------------------
%macro	hwint_slave 1	
	push	%1
	call	spurious_irq
	add	esp, 4
	hlt
%endmacro
; ---------------------------------

ALIGN	16
hwint08:		; Interrupt routine for irq 8 (realtime clock).
	hwint_slave	8

ALIGN	16
hwint09:		; Interrupt routine for irq 9 (irq 2 redirected)
	hwint_slave	9

ALIGN	16
hwint10:		; Interrupt routine for irq 10
	hwint_slave	10

ALIGN	16
hwint11:		; Interrupt routine for irq 11
	hwint_slave	11

ALIGN	16
hwint12:		; Interrupt routine for irq 12
	hwint_slave	12

ALIGN	16
hwint13:		; Interrupt routine for irq 13 (FPU exception)
	hwint_slave	13

ALIGN	16
hwint14:		; Interrupt routine for irq 14 (AT winchester)
	hwint_slave	14

ALIGN	16
hwint15:		; Interrupt routine for irq 15
	hwint_slave	15

; 中断和异常 -- 异常
divide_error:
	push	0xFFFFFFFF	; no err code
	push	0		; vector_no	= 0
	jmp	exception
single_step_exception:
	push	0xFFFFFFFF	; no err code
	push	1		; vector_no	= 1
	jmp	exception
nmi:
	push	0xFFFFFFFF	; no err code
	push	2		; vector_no	= 2
	jmp	exception
breakpoint_exception:
	push	0xFFFFFFFF	; no err code
	push	3		; vector_no	= 3
	jmp	exception
overflow:
	push	0xFFFFFFFF	; no err code
	push	4		; vector_no	= 4
	jmp	exception
bounds_check:
	push	0xFFFFFFFF	; no err code
	push	5		; vector_no	= 5
	jmp	exception
inval_opcode:
	push	0xFFFFFFFF	; no err code
	push	6		; vector_no	= 6
	jmp	exception
copr_not_available:
	push	0xFFFFFFFF	; no err code
	push	7		; vector_no	= 7
	jmp	exception
double_fault:
	push	8		; vector_no	= 8
	jmp	exception
copr_seg_overrun:
	push	0xFFFFFFFF	; no err code
	push	9		; vector_no	= 9
	jmp	exception
inval_tss:
	push	10		; vector_no	= A
	jmp	exception
segment_not_present:
	push	11		; vector_no	= B
	jmp	exception
stack_exception:
	push	12		; vector_no	= C
	jmp	exception
general_protection:
	push	13		; vector_no	= D
	jmp	exception
page_fault:
	push	14		; vector_no	= E
	jmp	exception
copr_error:
	push	0xFFFFFFFF	; no err code
	push	16		; vector_no	= 10h
	jmp	exception

exception:
	call	exception_handler
	add	esp, 4*2	; 让栈顶指向 EIP，堆栈中从顶向下依次是：EIP、CS、EFLAGS
	hlt

; ------------------------------------------------------------------------
; save
; ------------------------------------------------------------------------
save:
	; 不用再 esp - 4 了，因为 call save 时 会将返回地址压栈
	; 保存原寄存器值
	pushad
	push ds
	push es
	push fs
	push gs

	mov dx, ss
	mov ds, dx
	mov es, dx

	mov	esi, esp	; eax = 进程表起始地址

	inc dword [k_reenter]
	cmp dword [k_reenter], 0
	jne .1

	mov esp, StackTop	; 切到内核栈
	push	restart
	jmp	[esi + RETADR - P_STACKBASE]	; save return
.1:		; 中断重入
	push	restart_reenter
	jmp	[esi + RETADR - P_STACKBASE]	; save return
	
; ------------------------------------------------------------------------
; syscall
; ------------------------------------------------------------------------
sys_call:
	call	save
	push	dword [p_proc_ready]	; 将 write 的调用者传递给 sys_write
	sti

	push ecx
	push ebx
	call	[sys_call_table + eax * 4]
	add esp, 4 * 3

	mov	[esi + EAXREG - P_STACKBASE], eax

	cli
	ret

; ------------------------------------------------------------------------
; restart
; ------------------------------------------------------------------------
restart:
	mov esp, [p_proc_ready]		; esp 指向进程表
	lldt	[esp + P_LDT_SEL]	
	lea eax, [esp + P_STACKTOP]	; lea 加载有效地址 取出进程表中寄存器的末地址
	mov dword [tss + TSS3_S_SP0], eax	; 将末地址存入其对应的 tss 的 esp0 中
								; 在下一次中断开始时，从 tss.esp0 中找到内核栈栈顶，即进程表中寄存器的末地址
								; 可正确 push 进程全部的寄存器值
restart_reenter:
	dec	dword [k_reenter]		; 进程第一次运行之前就将 k_reenter 减 1，需将其初始值改为 0
	pop	gs
	pop fs
	pop es
	pop ds
	popad 
	add esp, 4
	iretd